package com.starsoft.frame.core.orm;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.hibernate.Hibernate;
import org.hibernate.Query;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.metadata.ClassMetadata;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.starsoft.frame.util.StringUtil;

@Component
@SuppressWarnings("unchecked")
public class HibernateDao{

	@Autowired
	private SessionFactory sessionFactory;
	
	@SuppressWarnings("rawtypes")
	private Map<String, Class> entityMap = new HashMap<>();

	public HibernateDao() {
	}
	
	public SessionFactory getSessionFactory() {
		return sessionFactory;
	}

	public void setSessionFactory(SessionFactory sessionFactory) {
		this.sessionFactory = sessionFactory;
	}

	public Session getSession() {
		return sessionFactory.getCurrentSession();
	}
	
	public void evict(Object ob){
		getSession().evict(ob);
	}

	public void initialize(Object entity){
		Hibernate.initialize(entity);
	}
	
	public Class<?> getMappedClass(String entityName){
		if(entityMap.containsKey(entityName)){
			return  entityMap.get(entityName);
		} else {
			Map<String,ClassMetadata> classes = sessionFactory.getAllClassMetadata();
			for(ClassMetadata classMeta : classes.values()){
				String name = classMeta.getEntityName();
				name = name.substring(name.lastIndexOf(".")+1);
				if(entityName.equalsIgnoreCase(name)){
					entityMap.put(entityName, classMeta.getMappedClass());
					return  classMeta.getMappedClass();
				}
			}
		}
		return null;
	}
	
	public <B> B get(Class<B> entityClass, Serializable id) {
		return (B) getSession().get(entityClass, id);
	}
	
	public <B> B get(String hql, Object... args) {
		List<B> list = find(hql, args);
		if(list.size()==1){
			return list.get(0);
		} else {
			return null;
		}
	}
	
	public <B> B  getByMap(Class<B> entityClass, Map<String, Object> params) {
		List<B> list = findByMap(entityClass, params);
		if(list.size()==1){
			return list.get(0);
		} else if(list.size()>1){
			throw new RuntimeException("Expect one , returu "+list.size());
		} else {
			return null;
		} 
	}

	public <B> List<B> getAll(Class<B> entityClass) {
		return getSession().createCriteria(entityClass).list();
	}
	
	public Object getValue(String hql, Object... args) {
		Query queryObject = getQuery(hql, args);
		return queryObject.uniqueResult();
	}
	
	public Object getValueByMap(String hql, Map<String, Object> params) {
		Query queryObject = getQueryByMap(hql, params);
		return queryObject.uniqueResult();
	}
	
	public Serializable save(Object entity) {
		return getSession().save(entity);
	}

	public void update(Object entity) {
		getSession().update(entity);
	}

	public void saveOrUpdate(Object entity) {
		getSession().saveOrUpdate(entity);
	}
	
	public void saveOrUpdateBatch(List<?> objs) {
		Session session = getSession();
		for (int i = 0; i < objs.size(); i++) {
			session.saveOrUpdate(objs.get(i));
			if (i % 100 == 0 && i > 0) {
				session.flush();
			}
		}
	}

	public int update(String hql, Object... args) {
		Query queryObject = getQuery(hql, args);
		return queryObject.executeUpdate();
	}

	public void delete(Object entity) {
		getSession().delete(entity);
	}

	public <B> void delete(Class<B> entityClass, Serializable... id) {
		for(int i=0;i<id.length;i++){
			Object ob = getSession().get(entityClass, id[i]);
			if(ob != null){
				getSession().delete(ob);
			}
		}
	}

	public <B> List<B> find(String hql, Object... args) {
		Query queryObject = getQuery(hql, args);
		return queryObject.list();
	}

	public <B> List<B> findByMap(String hql, Map<String, Object> params) {
		Query queryObject = getQueryByMap(hql, params);
		return queryObject.list();
	}
	
	public <B> List<B> findByMap(Class<B> entityClass, Map<String, Object> params) {
		DetachedCriteria ct=this.createCriteria(entityClass, params);
		return ct.getExecutableCriteria(getSession()).list();
	}
	
	public List<Map<String, Object>> findBySql(String sql, Object... args){
		SQLQuery query = getSession().createSQLQuery(sql);    
		query.setResultTransformer(CriteriaSpecification.ALIAS_TO_ENTITY_MAP);
		if (args != null) {
			for (int i = 0; i < args.length; i++) {
				query.setParameter(i, args[i]);
			}
		}
		return query.list();
	}
	
	public <B> Map<String, Object> pageQuery(Class<B> entityClass, PageObject po) {
		return this.pageQuery(entityClass, po, new HashMap<String, Object>());
	}
	
	@SuppressWarnings({ "rawtypes" })
	public <B> Map<String, Object> pageQuery(Class<B> entityClass, PageObject po, Map params) {
		DetachedCriteria ct=this.createCriteria(entityClass, params);
		ct.setProjection(Projections.rowCount());
		List tlst=ct.getExecutableCriteria(getSession()).list();
		long cntnum=(Long) (tlst.get(0));
		po.setTotal(cntnum);
		if (cntnum < 1){
			return po.getModel();
		}
		
		ct=this.createCriteria(entityClass, params);
		addCriteriaOrder(ct, po);
		List<B> datalst = ct.getExecutableCriteria(getSession()).setFirstResult(po.starIndex())
				.setMaxResults(po.getPageSize()).list();
		po.setRows(datalst);
		return po.getModel();
	}
	
	public <B> Map<String, Object> pageQuery(String hql, PageObject po, Object ...args) {
		int fromIndex = hql.toUpperCase().indexOf("FROM");
		String countHql = "select count(*) as recordCount "+hql.substring(fromIndex);
		Query query = getQuery(countHql, args);
		long cntnum=(Long) (query.uniqueResult());
		po.setTotal(cntnum);
		if (cntnum < 1){
			return po.getModel();
		}
		
		query = getQuery(hql, args);
		List<B> datalst = query.setFirstResult(po.starIndex()).setMaxResults(po.getPageSize()).list();
		po.setRows(datalst);
		return po.getModel();
	}
	
	@SuppressWarnings({ "rawtypes" })
	public <B> Map<String, Object> pageQuery(Class<B> entityClass, PageObject po, List<Criterion> criterions) {
		DetachedCriteria ct=this.createCriteria(entityClass, criterions);
		ct.setProjection(Projections.rowCount());
		List tlst=ct.getExecutableCriteria(getSession()).list();
		long cntnum=(Long) (tlst.get(0));
		po.setTotal(cntnum);
		if (cntnum < 1){
			return po.getModel();
		}
		
		ct=this.createCriteria(entityClass, criterions);
		addCriteriaOrder(ct, po);
		List<B> datalst = ct.getExecutableCriteria(getSession()).setFirstResult(po.starIndex())
				.setMaxResults(po.getPageSize()).list();
		po.setRows(datalst);
		return po.getModel();
	}
	
	@SuppressWarnings({ "rawtypes"})
	private <B> DetachedCriteria createCriteria(Class<B> entityClass, Map params){
		DetachedCriteria ct=DetachedCriteria.forClass(entityClass);
		Map _params = new HashMap();
		Field[] fields=entityClass.getDeclaredFields();
		for(Field field:fields){
			Object v=params.get(field.getName());
			if (v != null) {
				_params.put(field.getName(), v);
			}
		}
		ct.add(Restrictions.allEq(_params));
		return ct;
	}

	private <B> DetachedCriteria createCriteria(Class<B> entityClass, List<Criterion> criterions){
		DetachedCriteria ct=DetachedCriteria.forClass(entityClass);
		for(Criterion criterion:criterions){
			ct.add(criterion);
		}
		return ct;
	}
	
	private void addCriteriaOrder(DetachedCriteria ct, PageObject po) {
		if (!StringUtil.isEmpty(po.getSort())) {
			String[] sorts = po.getSort().split(",");
			String[] orders = po.getOrder().split(",");
			for (int i = 0; i < sorts.length; i++) {
				if (StringUtil.isEmpty(orders[i]) || orders[i].equals("desc")) {
					ct.addOrder(Order.desc(sorts[i]));
				} else {
					ct.addOrder(Order.asc(sorts[i]));
				}
			}
		}
	}
	
	public Query getQueryByMap(String hql, Map<String, Object> params){
		Query queryObject = getSession().createQuery(hql);
		String[] paramsName =queryObject.getNamedParameters();
		for(String paramName:paramsName){
			Object value=params.get(paramName);
			if(value==null){
				throw new RuntimeException("缺少参数："+paramName);
			}
			if (value instanceof Collection) {
				queryObject.setParameterList(paramName, (Collection<?>) value);
			} else if (value instanceof Object[]) {
				queryObject.setParameterList(paramName, (Object[]) value);
			} else {
				queryObject.setParameter(paramName, value);
			}
		}
		return queryObject;
	}
	
	public Query getQuery(String hql, Object... args) {
		Query queryObject = getSession().createQuery(hql);
		if (args != null) {
			for (int i = 0; i < args.length; i++) {
				String paramName = i + "";
				Object value = args[i];
				if (value instanceof Collection) {
					queryObject.setParameterList(paramName, (Collection<?>) value);
				} else if (value instanceof Object[]) {
					queryObject.setParameterList(paramName, (Object[]) value);
				} else {
					queryObject.setParameter(paramName, value);
				}
			}
		}
		return queryObject;
	}
}
